﻿using CS795.TravelAgency.Flight.Domain;
using System;
using System.Linq;
using System.Security.Permissions;
using System.ServiceModel;

namespace CS795.TravelAgency.Flight
{
    public class Service : IService
    {
        private FlightContext context = new FlightContext();
        private Random random = new Random();

        #region IService Functions
        [PrincipalPermission(SecurityAction.Demand, Authenticated = true)]
        public Quote RequestQuote(Request request)
        {
            CheckSeatType(request.Type);
            CheckLocations(request.Departure, request.Destination);
            CheckDate(DateTime.Now, request.Date);
            CheckCount(request.Count);

            string currentUser = ServiceSecurityContext.Current.PrimaryIdentity.Name;
            //string currentUser = "TravelAgency";
            Domain.Entities.User user = context.Users.Where(u => u.Name == currentUser).Single();
            decimal seatPrice = GenerateRandomPrice(request.Type);
            decimal totalPrice = request.Count * seatPrice;
            Domain.Entities.Quote quote = new Domain.Entities.Quote
            {
                Departure = request.Departure,
                Destination = request.Destination,
                Date = request.Date,
                Count = request.Count,
                Type = ConvertSeatType(request.Type),
                User = user,
                UserId = user.Id,
                Price = totalPrice
            };
            context.Quotes.Add(quote);
            context.SaveChanges();
            Quote result = new Quote
            {
                Id = quote.Id,
                PricePerSeat = seatPrice,
                TotalPrice = totalPrice
            };
            return result;
        }

        [PrincipalPermission(SecurityAction.Demand, Authenticated = true)]
        public int MakeReservation(int quoteId)
        {
            Domain.Entities.Quote quote = context.Quotes.Where(q => q.Id == quoteId).Single();
            int reservationCount = context.Reservations.Where(r => r.QuoteId == quoteId).Count();
            if (reservationCount > 0)
            {
                throw new ArgumentException("Reservation is already made");
            }

            Domain.Entities.Reservation reservation = new Domain.Entities.Reservation
            {
                Status = Domain.Entities.ReservationStatus.Reserved,
                QuoteId = quoteId,
                Quote = quote,
                Payment = null
            };
            context.Reservations.Add(reservation);
            context.SaveChanges();
            return reservation.Id;
        }

        [PrincipalPermission(SecurityAction.Demand, Authenticated = true)]
        public void CancelReservation(int reservationId)
        {
            Domain.Entities.Reservation reservation = context.Reservations.Where(r => r.Id == reservationId).Single();
            if (reservation.Status == Domain.Entities.ReservationStatus.Confirmed)
            {
                throw new ArgumentException("Cannot cancel a reservation that has already been confirmed!");
            }

            if (reservation.Status == Domain.Entities.ReservationStatus.Reserved)
            {
                reservation.Status = Domain.Entities.ReservationStatus.Cancelled;
                context.SaveChanges();
            }
        }

        [PrincipalPermission(SecurityAction.Demand, Authenticated = true)]
        public void ConfirmReservation(Confirmation confirmation)
        {
            Domain.Entities.Reservation reservation = context.Reservations.Where(r => r.Id == confirmation.ReservationId).Single();
            if (reservation.Status == Domain.Entities.ReservationStatus.Cancelled)
            {
                throw new ArgumentException("Cannot confirm cancelled reservation!");
            }

            if (string.IsNullOrEmpty(confirmation.Name))
            {
                throw new ArgumentException("Customer Name is required!");
            }

            if (string.IsNullOrEmpty(confirmation.AddressLine1))
            {
                throw new ArgumentException("Address is required!");
            }

            if (string.IsNullOrEmpty(confirmation.State) || confirmation.State.Length != 2)
            {
                throw new ArgumentException("State abbreviation is required!");
            }

            if (string.IsNullOrEmpty(confirmation.City))
            {
                throw new ArgumentException("City is required!");
            }

            if (string.IsNullOrEmpty(confirmation.ZipCode))
            {
                throw new ArgumentException("Zip Code is required!");
            }

            CheckCardType(confirmation.CardType);

            if (string.IsNullOrEmpty(confirmation.CardNumber))
            {
                throw new ArgumentException("Credit Card Number required!");
            }

            if (0 >= confirmation.CardExpirationMonth || 12 < confirmation.CardExpirationMonth)
            {
                throw new ArgumentException("Card Expiration Month invalid!");
            }

            if (confirmation.CardExpirationYear < DateTime.Now.Date.Year)
            {
                throw new ArgumentException("Card Expiration Year invalid!");
            }

            DateTime expiration = new DateTime(confirmation.CardExpirationYear, confirmation.CardExpirationMonth, 1);
            expiration = expiration.AddMonths(1);
            expiration = expiration.AddDays(-1);
            if (DateTime.Now > expiration)
            {
                throw new ArgumentException("Credit Card Expired!");
            }

            Domain.Entities.Payment payment = new Domain.Entities.Payment
            {
                Name = confirmation.Name,
                AddressLine1 = confirmation.AddressLine1,
                AddressLine2 = confirmation.AddressLine2,
                City = confirmation.City,
                State = confirmation.State,
                ZipCode = confirmation.ZipCode,
                CardNumber = confirmation.CardNumber,
                CardType = ConvertCardType(confirmation.CardType),
                CardExpirationMonth = confirmation.CardExpirationMonth,
                CardExpirationYear = confirmation.CardExpirationYear
            };

            context.Payments.Add(payment);
            reservation.Payment = payment;
            reservation.Status = Domain.Entities.ReservationStatus.Confirmed;
            context.SaveChanges();
        }
        #endregion

        #region Utility Functions
        private decimal GenerateRandomPrice(SeatType type)
        {
            int intResult = 0;
            switch (type)
            {
                case SeatType.EconomyClass:
                    intResult = random.Next(7500, 30000);
                    break;
                case SeatType.FirstClass:
                    intResult = random.Next(50000, 100000);
                    break;
                default:
                    throw new ArgumentException("Unrecognized seat type selected!");
            }
            decimal result = intResult;
            result /= 100;
            return result;
        }

        private void CheckSeatType(SeatType type)
        {
            switch (type)
            {
                case SeatType.EconomyClass:
                case SeatType.FirstClass:
                    return;
                default:
                    throw new ArgumentException("Unrecognized seat type selected!");
            }
        }

        private void CheckDate(DateTime current, DateTime request)
        {
            if (current.Date >= request.Date)
            {
                throw new ArgumentException("Flight Reservations must be for future dates!");
            }
        }

        private void CheckLocations(string departure, string destination)
        {
            if (string.IsNullOrEmpty(departure) || string.IsNullOrEmpty(destination))
            {
                throw new ArgumentException("Departure and Destination locations are required!");
            }
        }

        private void CheckCount(int count)
        {
            if (count < 1)
            {
                throw new ArgumentException("Requested seats must be greater than 1!");
            }
        }

        private void CheckCardType(CardType type)
        {
            switch (type)
            {
                case CardType.Visa:
                case CardType.Mastercard:
                case CardType.Discover:
                case CardType.AmericanExpress:
                    break;
                default:
                    throw new ArgumentException("Unrecognized card type selected!");
            }
        }

        private Domain.Entities.SeatType ConvertSeatType(SeatType type)
        {
            switch (type)
            {
                case SeatType.EconomyClass:
                    return Domain.Entities.SeatType.EconomyClass;
                case SeatType.FirstClass:
                    return Domain.Entities.SeatType.FirstClass;
                default:
                    throw new ArgumentException("Unrecognized seat type selected!");
            }
        }

        private Domain.Entities.CardType ConvertCardType(CardType type)
        {
            switch (type)
            {
                case CardType.Visa:
                    return Domain.Entities.CardType.Visa;
                case CardType.Mastercard:
                    return Domain.Entities.CardType.Mastercard;
                case CardType.Discover:
                    return Domain.Entities.CardType.Discover;
                case CardType.AmericanExpress:
                    return Domain.Entities.CardType.AmericanExpress;
                default:
                    throw new ArgumentException("Unrecognized card type selected!");
            }
        }
        #endregion
    }
}
